package board;

import java.util.LinkedList;

import sgfRead.SgfPosition;

/*
 ____
 | __} bMargin
 | |_| }cSize

 */

public class GoBoard {
	//
	public static final int _BLACK = 0;
	public static final int _WHITE = 1;

	// for process nearby position
	public static final int _CAPTURED = 0; // indicate that there is the
											// capture.
	public static final int _NOPE = 1; // indicate that nothing happen.

	// parameter to draw board.
	int type; // type of board: 9x9, 13x13, 19x19 ...
	int bSize; // board size: width=height
	int cSize; // cell size: width = height
	int bMargin; // board margin.
	int pSize; // piece size, piece radius.
	// storage data for pieces.
	GoPosition[][] board;
	// list to store previos move
	LinkedList<GoMove> lstMove;

	public GoBoard(int type, int bSize) {
		this.type = type;
		this.bSize = bSize;
		this.doPreapareBoard();
		lstMove = new LinkedList<GoMove>();
	}

	public void updateSize(int boardsize) {
		this.bSize = boardsize;
		this.updateSize();
	}

	public void updateSize() {
		// calculate all info.
		// cSize
		cSize = bSize / type;
		// bMargin
		bMargin = (bSize - cSize * (type - 1)) / 2;
		// pSize = 80% of cell size;
		pSize = (80 * cSize / 100) / 2;
	}

	public void clearBoard() {
		for (int i = 0; i < this.type; i++) {
			for (int j = 0; j < this.type; j++) {
				board[i][j].setEmpty();
			}
		}
	}

	private void doPreapareBoard() {
		// create board data.
		this.board = new GoPosition[this.type][this.type];
		for (int i = 0; i < this.type; i++) {
			for (int j = 0; j < this.type; j++) {
				board[i][j] = new GoPosition(i, j);
			}
		}
		this.updateSize();
	}

	public int getMargin() {

		return this.bMargin;
	}

	public int getBoardSize() {
		//
		return this.bSize;
	}

	public int getCellSize() {
		return this.cSize;
	}

	public int getBoardType() {

		return type;
	}

	public GoPosition getPosition(int i, int j) {
		return board[i][j];
	}

	public boolean putStone(SgfPosition pos) {
		GoPosition gopos = new GoPosition(pos);
		gopos = this.getPosition(gopos.getX(), gopos.getY());
		if (gopos != null) {
			if (pos.isBlack()) {
				if (this.putStone(gopos, _BLACK)) {
					saveMove(gopos);
					return true;
				}
			} else if (pos.isWhite()) {
				if (this.putStone(gopos, _WHITE)) {
					saveMove(gopos);
					return true;
				}
			}
		}

		return false;
	}

	public boolean putWhiteStone(int i, int j) {
		GoPosition gopos = this.getPosition(i, j);
		if (gopos != null) {
			if (this.putStone(gopos, _WHITE)) {
				saveMove(gopos);
				return true;
			}
		}
		return false;
	}

	public boolean putBlackStone(int i, int j) {
		GoPosition gopos = this.getPosition(i, j);
		if (gopos != null)
			if (this.putStone(gopos, _BLACK)) {
				saveMove(gopos);
				return true;
			}
		return false;
	}

	public boolean putStone(GoMove move) {
		GoPosition pos = this.getPosition(move.getX(), move.getY());

		if (move.isWhite()) {
			if (this.putStone(pos, _WHITE)) {
				saveMove(pos);
				return true;
			}
		} else if (move.isBlack()) {
			if (this.putStone(pos, _BLACK)) {
				saveMove(pos);
				return true;
			}
		}
		return false;

	}

	private boolean putStone(GoPosition pos, int color) {
		// check if this pos is available
		if (!pos.isEmpty()) {
			return false;
		}
		// first try to put stone
		if (color == _WHITE)
			pos.setWhite();
		if (color == _BLACK)
			pos.setBlack();
		// second: process nearby rival group
		int cap = this.processNearbyRival(pos);
		if (cap == _CAPTURED) { // stone put there is ok
			return true;
		}
		// third: if no group is captured, so need check liberty itself.
		int lib = this.countLibertyGroup(pos);
		if (lib == 0) {// oh my god, kill yourself?
			// step back
			pos.setEmpty();
			return false;
		}
		// come here: so ok.
		return true;
	}

	private void saveMove(GoPosition pos) {
		GoMove move = new GoMove(pos);
		this.lstMove.add(move);
		// this.lstMove.

	}

	/*
	 * this function incharge of check all rival group nearby. if rival group is
	 * empty liberty kill all and return Captured. notes: Process all rival
	 * group.
	 */
	public int processNearbyRival(GoPosition pos) {
		int ret = _NOPE; // default is nothing

		for (GoPosition tpos : getNearbyPosition(pos)) {
			if (tpos != null && !tpos.isEmpty()
					&& tpos.getType() != pos.getType()) {
				int count = this.countLibertyGroup(tpos);
				if (count == 0) { // definately dead, he he
					this.removeGroup(tpos);
					ret = _CAPTURED;
				}
			}
		}

		return ret;
	}

	public GoPosition[] getNearbyPosition(GoPosition pos) {
		GoPosition[] gpos = new GoPosition[4];
		gpos[0] = this.getUpPosition(pos.getX(), pos.getY());
		gpos[1] = this.getDownPosition(pos.getX(), pos.getY());
		gpos[2] = this.getLeftPosition(pos.getX(), pos.getY());
		gpos[3] = this.getRightPosition(pos.getX(), pos.getY());
		return gpos;
	}

	public boolean isInBoard(int x, int y) {
		return (x >= 0 && x < type && y >= 0 && y < type);
	}

	public GoPosition getUpPosition(int x, int y) {
		x = x;
		y = y - 1;
		if (isInBoard(x, y))
			return board[x][y];
		else
			return null;

	}

	public GoPosition getDownPosition(int x, int y) {
		x = x;
		y = y + 1;
		if (isInBoard(x, y))
			return board[x][y];
		else
			return null;

	}

	public GoPosition getLeftPosition(int x, int y) {
		x = x - 1;
		y = y;
		if (isInBoard(x, y))
			return board[x][y];
		else
			return null;

	}

	public GoPosition getRightPosition(int x, int y) {
		x = x + 1;
		y = y;
		if (isInBoard(x, y))
			return board[x][y];
		else
			return null;

	}

	public void removeGroup(GoPosition pos) {
		if (pos.isEmpty())
			return;
		boolean[][] check = new boolean[this.type][this.type];
		for (int i = 0; i < this.type; i++) {
			for (int j = 0; j < this.type; j++) {
				check[i][j] = false;
			}
		}
		// mark all position belong to this group.
		markGroup(pos, check, pos.getType());
		// remove all pieces on this group
		for (int i = 0; i < this.type; i++) {
			for (int j = 0; j < this.type; j++) {
				if (check[i][j] == true) {
					board[i][j].setEmpty();
				}
			}
		}
	}

	public int countLibertyGroup(GoPosition pos) {
		if (pos.isEmpty())
			return -1;

		boolean[][] check = new boolean[this.type][this.type];
		for (int i = 0; i < this.type; i++) {
			for (int j = 0; j < this.type; j++) {
				check[i][j] = false;
			}
		}
		int count = 0;
		// mark all position belong to this group.
		count = countLiberty(pos, check, count, pos.getType());
		return count;
	}

	/*
	 * this function is used in countLibertyGroup function. for purpose of
	 * recusive activity.
	 */
	private int countLiberty(GoPosition pos, boolean[][] check, int count,
			int type) {

		if (pos == null || check[pos.getX()][pos.getY()])
			return count;
		if (pos.isEmpty()) {
			count++;
			return count;
		}
		if (pos.getType() != type)
			return count;
		check[pos.getX()][pos.getY()] = true;
		count = countLiberty(getUpPosition(pos.getX(), pos.getY()), check,
				count, type);
		count = countLiberty(getDownPosition(pos.getX(), pos.getY()), check,
				count, type);
		count = countLiberty(getLeftPosition(pos.getX(), pos.getY()), check,
				count, type);
		count = countLiberty(getRightPosition(pos.getX(), pos.getY()), check,
				count, type);
		return count;
	}

	private void markGroup(GoPosition pos, boolean[][] check, int type) {
		if (pos == null || check[pos.getX()][pos.getY()]
				|| pos.getType() != type)
			return;
		check[pos.getX()][pos.getY()] = true;
		markGroup(getUpPosition(pos.getX(), pos.getY()), check, type);
		markGroup(getDownPosition(pos.getX(), pos.getY()), check, type);
		markGroup(getLeftPosition(pos.getX(), pos.getY()), check, type);
		markGroup(getRightPosition(pos.getX(), pos.getY()), check, type);
	}

	public int getPieceSize() {
		return this.pSize;
	}

	public void doUndo() {
		if (this.lstMove.isEmpty())
			return;

		this.lstMove.removeLast();
		this.doReplayListMove();
	}

	private void doReplayListMove() {
		LinkedList<GoMove> lst = new LinkedList<GoMove>();
		// copy
		while (!this.lstMove.isEmpty()) {
			lst.add(this.lstMove.removeFirst());
		}
		// clear
		this.clearBoard();
		// replay
		while (!lst.isEmpty()) {
			GoMove move = lst.removeFirst();
			this.putStone(move);
		}
	}

	public GoPosition getLastMove() {
		if(this.lstMove.isEmpty())
			return null;
		GoMove go = this.lstMove.getLast();
		return this.getPosition(go.getX(), go.getY());
	}
}
